Skip to content

fix(weave): refuse code-bearing custom objects on server-side decode#7004

Merged
gtarpenning merged 11 commits into
masterfrom
gtarpenning/wb-34909-server-decode-guard
Jun 2, 2026
Merged

fix(weave): refuse code-bearing custom objects on server-side decode#7004
gtarpenning merged 11 commits into
masterfrom
gtarpenning/wb-34909-server-decode-guard

Conversation

@gtarpenning

@gtarpenning gtarpenning commented May 28, 2026

Copy link
Copy Markdown
Member

Summary

  • Server-side workers (eval) reconstruct user payloads; an Op, or any custom type that hits the packaged load_op fallback, runs user-uploaded code. Adds WeaveClient.allow_unsafe_custom_obj_decode (default True); the worker flips it off.
  • With it off, _decode_custom_obj still decodes data-only types (images, audio) via their in-process serializer, but refuses Op/unknown types and the load_op fallback, so multimodal eval datasets keep working while code never loads. No active client fails closed.
  • Also rejects op refs passed as the eval/model ref, which client.get would run before decode applies. Tracking: WB-34909

Testing

unit tests for is_safe_to_decode + the ref guard + KNOWN_TYPES sync; decode tests for data-only decode under the worker policy, Op refusal, and the forged load_op fallback; e2e eval rejects an Op dataset row at decode time.

Server-side workers (the evaluate-model worker) reconstruct user-supplied
objects. Gate custom-object deserialization on a per-client policy
(WeaveClient.allow_unsafe_custom_obj_decode, default True) so workers can
refuse to reconstruct Op / load_op-backed custom types at decode time,
including dataset rows materialized lazily during evaluation.

https://coreweave.atlassian.net/browse/WB-34909

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented May 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.85714% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
weave/trace/serialization/custom_objs.py 86.66% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

gtarpenning and others added 5 commits June 1, 2026 10:25
…il closed

Refines the server-side decode guard (WB-34909):
- is_safe_to_decode is now type-level: data-only custom types (images, audio,
  ...) decode via their in-process serializer, so worker evals on multimodal
  datasets keep working. Op and unknown types are still refused.
- Block the packaged load_op fallback when unsafe decode is off, closing the
  force-fallback path (name a safe type, ship bad bytes so its serializer
  throws, then reach the attacker's op).
- No active client now fails closed (data-only still decodes) with an
  actionable warning, instead of fail-open.
- Wire OP_CUSTOM_WEAVE_TYPE into the sync test (was dead) and document the
  process-global client dependency the guard relies on.
- Fix a KNOWN_TYPES test leak that rebound the name instead of restoring the set.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Rename WeaveClient.allow_unsafe_custom_obj_decode -> _allow_unsafe_custom_obj_decode
  so the scary-named, server-only policy doesn't surface in user autocomplete.
- Tighten the decode-guard comments toward what the code does (correctness) rather
  than attack framing; fix a stale test-name reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gtarpenning gtarpenning marked this pull request as ready for review June 1, 2026 22:27
@gtarpenning gtarpenning requested a review from a team as a code owner June 1, 2026 22:27
"""
ref = Ref.parse_uri(ref_uri)
if not isinstance(ref, ObjectRef):
if not isinstance(ref, ObjectRef) or isinstance(ref, OpRef):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need the OpRef check? Now the func name is a bit confusing

@gtarpenning gtarpenning Jun 1, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the decode guard in custom_objs.py fires on CustomWeaveType payloads so technically if you had an Op inside a dataset row, we would then blindly call the load_op, so we just check to make sure you don't have an op here.
I think this also protects the initial worker call: client.get(Ref.parse_uri(evaluation_ref)). If evaluation_ref is an op ref, client.get loads and runs that op directly...

Comment thread weave/trace_server/workers/evaluate_model_worker/evaluate_model_worker.py Outdated
Comment thread weave/trace/weave_client.py
@jtschoonhoven

Copy link
Copy Markdown
Contributor

I attempted something similar here (now closed): #6623

Comment thread tests/trace_server/workers/test_evaluate_model_worker.py


@pytest.mark.parametrize(
"encoded",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Comment thread weave/trace/weave_client.py Outdated
Comment on lines +401 to +403
# Internal: server-side workers (eval, scoring) flip this off so deserialization
# never reconstructs code-bearing custom objects. See custom_objs.py.
self._allow_unsafe_custom_obj_decode = True

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like something I should be able to set from an env var. Customers might also want to disable remote code execution, even if it's supposedly their own code.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, but this is the weave client, being run in the weave worker 🤔 not sure how we expose it to our backend as an env var but not to users. It would be totally useless if a user set this in their own client, or worse, it would break things that are fine for users on their own compute.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be totally useless if a user set this in their own client, or worse, it would break things that are fine for users on their own compute.

As a user of weave I want the ability to disable local execution of code that's hosted remotely. Are many users relying on deserializing code-bearing objects? I know some users publish and share ops, but I don't have the sense that it's especially common.

Also, if there was an env var then we could set ALLOW_UNSAFE_CUSTOM_OBJ_DECODE=false in the helm charts and disable the behavior globally, rather than doing it in code service-by-service.

gtarpenning and others added 2 commits June 1, 2026 16:18
- rename _assert_object_ref -> _assert_non_op_ref (OpRef subclasses ObjectRef)
- add require_secure_weave_client helper; worker uses it
- add ids= to parametrized decode tests

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…9-review

# Conflicts:
#	weave/trace_server/workers/evaluate_model_worker/evaluate_model_worker.py

@jtschoonhoven jtschoonhoven left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments. I still recommend reading _allow_unsafe_custom_obj_decode from an env var, but otherwise this looks like a straightforward improvement.

Comment thread weave/trace/weave_client.py Outdated
Comment on lines +401 to +404
# Internal: server-side workers (eval, scoring) flip this off via
# `require_secure_weave_client` so deserialization never reconstructs
# code-bearing custom objects. See custom_objs.py.
self._allow_unsafe_custom_obj_decode = True

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I was a user and I read this block I would feel concerned 🤨

Probably worth writing the comment with customers in mind, to explain why this should be safe for them.

Also as a user I might want the same mechanism to disable code-bearing custom objects.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

# An op ref passed as the evaluation/model ref would be loaded and run by
# `client.get` directly, before the decode guard applies, so reject it here.
_assert_non_op_ref(args.evaluation_ref, "evaluation_ref")
_assert_non_op_ref(args.model_ref, "model_ref")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment is wrong and client.get now would raise an UnsafeDeserializationError. So the _assert_non_op_ref isn't necessary. But if I'm wrong that seems like a problem, since client.get should definitely check _allow_unsafe_custom_obj_decode

Comment thread weave/trace/serialization/custom_objs.py Outdated
Comment thread weave/trace/serialization/custom_objs.py
gtarpenning and others added 3 commits June 2, 2026 11:05
… op-ref check

Adds `WEAVE_ALLOW_UNSAFE_CUSTOM_OBJ_DECODE` (UserSettings) so deployments can
disable code-bearing custom-obj decode globally and users can opt into hardening;
the decode guard ANDs the setting with the per-client flag. Removes the redundant
`_assert_non_op_ref` (op refs already raise UnsafeDeserializationError via the
decode guard on client.get) and adds File to the safe/known type sets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Regression guard for the deleted `_assert_safe_ref` pre-check (WB-34909). An
op ref passed directly as evaluation_ref/model_ref must raise
UnsafeDeserializationError at `client.get` decode time, not import user code.
Currently green; nothing else pins that the decode guard subsumes the dropped
recursive payload check, so a future refactor of op loading could silently
re-open RCE on the eval worker.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gtarpenning gtarpenning enabled auto-merge (squash) June 2, 2026 22:04
@gtarpenning gtarpenning merged commit 6d6d6a1 into master Jun 2, 2026
423 of 505 checks passed
@gtarpenning gtarpenning deleted the gtarpenning/wb-34909-server-decode-guard branch June 2, 2026 22:42
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 2, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants